import copy
import math
import random
import sys
import time

import numpy as np

import config
from ExceptionHandler import NoAvailableRoomTimeException, StudentTimeException, TeacherTimeException, RoomTimeException

'''种群初始化(随机性) spf'''


def Load(task_list, students, teachers, classroom_list, pop):
    schemesList = []  # 保存排课方案的总列表
    RTmatrix_filled = []  # 存储填充课程信息后的教室时间矩阵列表
    STmatrix_filled = []  # 保存学生时间矩阵列表
    TTmatrix_filled = []  # 保存老师时间矩阵列表
    # student_schemes = []
    # teacher_schemes = []

    for i in range(pop):
        try:
            schemeList, RT, ST, TT = generate_schedule(task_list, students, teachers, classroom_list)
            schemesList.append(schemeList)
            RTmatrix_filled.append(RT)  # 存储填充课程信息后的教室时间矩阵列表
            STmatrix_filled.append(ST)  # 保存学生时间矩阵列表
            TTmatrix_filled.append(TT)  # 保存老师时间矩阵列表
            # student_schemes.append(student_scheme)
            # teacher_schemes.append(teacher_scheme)
            print(f"第{i}个方案生成成功。")
        except NoAvailableRoomTimeException as e:
            print(e)
            sys.exit(1)
    return schemesList, RTmatrix_filled, STmatrix_filled, TTmatrix_filled


'''修改课程冲突检测逻辑  spf  2024.3.2'''
'''添加教室时间表状态，随机选的时候去掉不可用的时间片和教室组合，不可用的在矩阵中标志为教学任务号  2024.4.13  SPF'''
'''添加起始周  2024.4.27  SPF'''
'''添加单双周  2024.5.7  SPF'''


def generate_schedule(task_list, students, teachers, classroom_list):
    # 复制课程列表以避免修改原始数据
    task_list_copy = copy.deepcopy(task_list)
    # student_list_copy = copy.deepcopy(student_list)
    # teacher_list_copy = copy.deepcopy(teacher_list)

    # 初始化教室时间表,学生时间表，教师时间表
    RT, ST, TT = init_timetables(classroom_list, students, teachers)

    # 教室限制：教室按人数排序,在这个教室某个时间段拍过课后就去掉这个教室,
    # classroom_list_copy = copy.deepcopy(classroom_list)
    # '''如果排好序让选择最好的话，后面的收敛曲线效果可能不好。因为一开始就初始化了最优的 （待讨论） 2024.4.13  SPF'''
    # 先排大班课（避免以后教室  2024.4.20）
    task_list_sorted = sorted(task_list_copy, key=lambda x: -len(x[6]))

    time1 = time.process_time()
    # 冲突检测
    # 遍历每门课程并分配教室和时间段
    for course in task_list_sorted:
        # 获取教学任务起始周
        start_week = course[7]
        end_week = course[8]
        # 获取教学任务是否是单双周(0表示不是单双周，1表示是单周，2表示是双周)
        week_type = course[11]
        # 获取课程的课时数(一周上几次课)
        jieci = math.floor(course[9] / 2)
        # 每节课分配教室
        for i in range(jieci):
            classroom = None
            time_slot = None
            # 随机选一个可用教室-时间片组合
            RT_arr = np.array(RT[start_week - 1], dtype=object)
            RT_aviliable = np.where(RT_arr == "")
            RT_aviliable_arr = np.array(RT_aviliable)

            # 判断是否有可用教室时间片可供排课----加入周次的因素
            if (len(RT_aviliable_arr[0]) > 0):
                while classroom is None or time_slot is None:
                    # 随机选择一个教室
                    # classroom = random.choice(classroom_list)
                    r_index = random.randrange(0, len(RT_aviliable_arr[0]))
                    r_t = RT_aviliable_arr[:, r_index]

                    # j = random.randrange(0, len(RT_aviliable_arr[0]))
                    # classroom = classroom_list[RT_aviliable_arr[0][j]]
                    classroom = classroom_list[r_t[0]]
                    # 随机选择一个时间段
                    # time_slot = random.randint(1, 35)
                    # time_slot = (RT_aviliable_arr[1][j] + 1)
                    time_slot = (r_t[1] + 1)

                    # 先判断教室人数是否够，在进行冲突检测
                    if classroom[1] < len(course[6]):
                        # 教室无法容纳学生人数，请重试
                        classroom = None
                        time_slot = None
                        time2 = time.process_time()
                        if (int)(time2 - time1) >= 60:
                            raise NoAvailableRoomTimeException("教室大小不够！")
                        continue
                    # 判断单双周,以及教室是否是空----周教室冲突   SPF   2024.5.7
                    # 不是单双周
                    week_room_conflict = False
                    stu_conflict = False
                    tea_conflict = False
                    if week_type == 0:
                        for i in range(start_week, end_week + 1):
                            # 教室时间冲突检测
                            if RT[i - 1][r_t[0]][r_t[1]] != "":
                                week_room_conflict = True
                                break
                            # 教师时间冲突
                            if TT[i - 1][teachers.index(course[3])][r_t[1]] != "":
                                tea_conflict = True
                                break
                            # 学生时间冲突
                            for stu in course[6]:
                                if ST[i - 1][students.index(stu)][r_t[1]] != "":
                                    stu_conflict = True
                                    break
                            if stu_conflict:
                                break
                    # 单周
                    if week_type == 1:
                        for i in range(start_week, end_week + 1):
                            if i % 2 == 1:
                                if RT[i - 1][r_t[0]][r_t[1]] != "":
                                    week_room_conflict = True
                                    break
                                    # 教师时间冲突
                                    if TT[i - 1][teachers.index(course[3])][r_t[1]] != "":
                                        tea_conflict = True
                                        break
                                    # 学生时间冲突
                                    for stu in course[6]:
                                        if ST[i - 1][students.index(stu)][r_t[1]] != "":
                                            stu_conflict = True
                                            break
                                    if stu_conflict:
                                        break
                    # 双周
                    if week_type == 2:
                        for i in range(start_week, end_week + 1):
                            if i % 2 == 0:
                                if RT[i - 1][r_t[0]][r_t[1]] != "":
                                    week_room_conflict = True
                                    break
                                    # 教师时间冲突
                                    if TT[i - 1][teachers.index(course[3])][r_t[1]] != "":
                                        tea_conflict = True
                                        break
                                    # 学生时间冲突
                                    for stu in course[6]:
                                        if ST[i - 1][students.index(stu)][r_t[1]] != "":
                                            stu_conflict = True
                                            break
                                    if stu_conflict:
                                        break
                    # 教室有冲突
                    if week_room_conflict:
                        classroom = None
                        time_slot = None
                        time2 = time.process_time()
                        if (int)(time2 - time1) >= 60:
                            raise RoomTimeException("教室有冲突！")
                        continue
                    # 学生有冲突
                    if stu_conflict:
                        classroom = None
                        time_slot = None
                        time2 = time.process_time()
                        if (int)(time2 - time1) >= 60:
                            raise StudentTimeException("学生有冲突！")
                        continue
                    # 教师有冲突
                    if tea_conflict:
                        classroom = None
                        time_slot = None
                        time2 = time.process_time()
                        if (int)(time2 - time1) >= 60:
                            raise TeacherTimeException("教师有冲突！")
                        continue

                    # 教室可容纳学生人数，并且一约束都满足的情况下更新三个时间表跟教学任务信息
                    # 将教室和时间段添加到课程
                    course.append((classroom, time_slot))
                    # 更新时间表
                    if week_type == 0:
                        for i in range(start_week, end_week + 1):
                            RT[i - 1][classroom_list.index(classroom)][time_slot - 1] = (
                                course[0], classroom[1], len(course[6]))
                            TT[i - 1][teachers.index(course[3])][time_slot - 1] = (course[0], classroom)
                            for stu in course[6]:
                                ST[i - 1][students.index(stu)][time_slot - 1] = (course[0], classroom)
                    if week_type == 1:
                        for i in range(start_week, end_week + 1):
                            if week_type % 2 == 1:
                                RT[i - 1][classroom_list.index(classroom)][time_slot - 1] = (
                                    course[0], classroom[1], len(course[6]))
                                TT[i - 1][teachers.index(course[3])][time_slot - 1] = (course[0], classroom)
                                for stu in course[6]:
                                    ST[i - 1][students.index(stu)][time_slot - 1] = (course[0], classroom)
                    if week_type == 1:
                        for i in range(start_week, end_week + 1):
                            if week_type % 2 == 0:
                                RT[i - 1][classroom_list.index(classroom)][time_slot - 1] = (
                                    course[0], classroom[1], len(course[6]))
                                TT[i - 1][teachers.index(course[3])][time_slot - 1] = (course[0], classroom)
                                for stu in course[6]:
                                    ST[i - 1][students.index(stu)][time_slot - 1] = (course[0], classroom)

            else:
                raise NoAvailableRoomTimeException("教室数量有限，请选择与教室资源相适应的教学任务！")

    return task_list_copy, RT, ST, TT


'''初始化时间表 spf'''


def init_timetables(classroom_list, students, teachers):
    RT = [[[""] * (config.num_days * config.periods_per_day) for i in range(len(classroom_list))] for j in
          range(config.MAX_WEEK)]  # 35是35个时间段,18周
    ST = [[[""] * (config.num_days * config.periods_per_day) for i in range(len(students))] for j in
          range(config.MAX_WEEK)]
    TT = [[[""] * (config.num_days * config.periods_per_day) for i in range(len(teachers))] for j in
          range(config.MAX_WEEK)]
    return RT, ST, TT
